#! /bin/sh

# tovid-init
# Part of the tovid suite
# =======================
# Define global (suite-wide) functions and variables
# for the tovid suite. 
#
# Project homepage: http://www.tovid.org
#
#
# Copyright (C) 2005 tovid.org <http://www.tovid.org>
# 
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later 
# version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Or see:
#
#           http://www.gnu.org/licenses/gpl.txt


# ******************************************************************************
# ******************************************************************************
#
#
# VARIABLES
#
#
# ******************************************************************************
# ******************************************************************************

# Reset input field separator to default
#IFS=

# Exit with error on undeclared variables
#set -u

# Suite version
TOVID_VERSION="@VERSION@"

# String used to separate blocks of output
SEPARATOR="========================================================="

TOVID_HOME="$HOME/.tovid"
TOVID_HOME_PAGE="http://www.tovid.org"
TOVID_FORUMS="http://www.createphpbb.com/tovid/"


# ******************************************************************************
# ******************************************************************************
#
#
# FUNCTIONS
#
#
# ******************************************************************************
# ******************************************************************************


# ******************************************************************************
# Return the full, absolute pathname of a given file or directory
# Echoes result to stdout, so in order to use the output as a "return value",
# call the function like this:
#     RETURN_VALUE=$(abspath "$FILENAME")
# ******************************************************************************
function abspath()
{
    DIR=$(dirname "$1")
    BASE=$(basename "$1")
    ABS_PATH="`cd \"$DIR\" && pwd || echo \"$DIR\"`/$BASE"
    echo "$ABS_PATH"
}

# ******************************************************************************
# Create a unique temporary directory and return its name
# Input args: $1 = Base name (i.e., "work_dir")
# Return value is echoed to stdout (i.e., "work_dir.3")
# ******************************************************************************
function tempdir()
{
    NUM=0
    BASENAME="$1"
    TEMPDIR="$BASENAME.$NUM"
    while test -d "$BASENAME.$NUM"; do
        ((NUM++))
    done
    mkdir "$BASENAME.$NUM"
    echo "$BASENAME.$NUM"
}

# ******************************************************************************
# Verify that a variable meets certain conditions
# Usage: verify $VAR set|range "test limits"
# Input: $1 = the variable to check
#        $2 = the kind of test to perform (set|range)
#             set: test if $VAR is in the space-separated set "test limits"
#             range: test if $VAR is in the range given by "test limits"
#        $3 = the limits for the test
#
# ex: verify $CMD_LN_OPT set "y n Y N"
#     will return ":" (true) if $CMD_LN_OPT is one of "y n Y N"
#     or retern "false" if it isn't (so if $CMD_LN_OPT was "no", you'd get "false")
#
# ex: verify $CMD_LN_OPT range "0 10"
#     will return ":" (true) if 0 <= $CMD_LN_OPT <= 10
# ******************************************************************************
function verify()
{
  VERIFY_VAR="$1"
  VERIFY_TEST_TYPE="$2"
  case $VERIFY_TEST_TYPE in
     "range" )
     VERIFY_LOW=$(echo "$3" | awk '{ print $1 }')
     VERIFY_HIGH=$(echo "$3" | awk '{ print $2 }')

     if test $VERIFY_LOW -le $VERIFY_VAR && \
        test $VERIFY_HIGH -ge $VERIFY_VAR
     then
        echo ":"
     else
        echo "false"
     fi 
     ;;

     "set" )
     VERIFY_SET="$3"

     if echo "$VERIFY_SET" | grep -w "$VERIFY_VAR" >> /dev/null 2>&1; then
         echo ":"
     else
         echo "false"
     fi
     ;;
  esac
}

# ******************************************************************************
# Print a pretty (wrapped) notice message.
# Args: $@ == text string containing the message 
# ******************************************************************************
function precho()
{
  echo -e "$@" | fold -s -w ${COLUMNS:-80}
}

# ******************************************************************************
# Print error message, then exit.
# Args: $@ == text string containing error message 
# ******************************************************************************
function exit_with_error()
{
    echo $@
    exit 1
}

# ******************************************************************************
# Take an integer number of seconds and format it as hours:minutes:seconds
# Echoes result to stdout, so in order to use the output as a "return value",
# call the function like this:
#     RETURN_VALUE=$(format_time $NUM_SECONDS)
# ******************************************************************************
function format_time()
{
    HMS_HOURS=$(expr $1 / 3600)
    HMS_MINS=$(expr \( $1 % 3600 \) / 60)
    HMS_SECS=$(expr $1 % 3600 % 60)

    test "$HMS_HOURS" -lt 10 && HMS_HOURS="0${HMS_HOURS}"
    test "$HMS_MINS" -lt 10 && HMS_MINS="0${HMS_MINS}"
    test "$HMS_SECS" -lt 10 && HMS_SECS="0${HMS_SECS}"

    echo "${HMS_HOURS}:${HMS_MINS}:${HMS_SECS}"
}

# ******************************************************************************
# Take a string containing a time (like "02:15:25.3") and
# format it as an integer number of seconds. Fractional seconds
# are truncated. Result is echoed to stdout, so to use the output
# as a "return value", call the function like this:
#     RETURN_VALUE=$(unformat_time $TIME_STRING)
# ******************************************************************************
function unformat_time()
{
    HMS_HOURS=$(awk -F ':' '{print $1}' <<< $1)
    HMS_MINS=$(awk -F ':' '{print $2}' <<< $1)
    HMS_SECS=$(awk -F ':' '{print $3}' <<< $1)
    TOT_SECONDS=$(expr $HMS_HOURS \* 3600 + $HMS_MINS \* 60 + $HMS_SECS)
    echo $TOT_SECONDS
}


# ******************************************************************************
# Display a progress meter showing MB written to a file
# Args: $1 = name of file to monitor
#       $2 = a short message to display, such as "Encoding video"
# ******************************************************************************
function file_output_progress()
{
    if $FAKE; then
        return
    fi
    FOP_OUTFILE="$1"
    FOP_BASENAME=$(basename "$FOP_OUTFILE")
    FOP_MSG="$2"
    # A dumb little animation toggle
    FOP_FLIP=false

    printf "\n"

    # Wait for input file to appear
    # After a 30-second timeout, exit gracefully
    CUR_TIME=30
    while test $CUR_TIME -gt 0; do
        # If file exists, wait a few more seconds, then break out
        if test -e "$FOP_OUTFILE"; then
            printf "Processing started. Please wait...                                               "
            sleep 3s
            break
        fi
        printf "Waiting $CUR_TIME seconds for output file \"$FOP_BASENAME\" to appear...\r"
        sleep 1s
        CUR_TIME=$(expr $CUR_TIME - 1)
    done

    printf "\n"

    # If file does not exist, exit with a runtime error
    if test ! -e "$FOP_OUTFILE"; then
        runtime_error "Couldn't create file: \"$FOP_OUTFILE\""
    fi
    
    # File size in bytes
    FOP_LAST_SIZE=0
    FOP_CUR_SIZE=$(du -b "$FOP_OUTFILE" | awk '{print $1}')
    
    # Keep looping until outfile stops getting bigger
    while test "$FOP_CUR_SIZE" -gt "$FOP_LAST_SIZE"; do
        # Display a changing line
        if $FOP_FLIP; then
            FOP_FLIP=false
            ANIM_STR="||| "
        else
            FOP_FLIP=:
            ANIM_STR="--- "
        fi

        # Display completion status
        FOP_CUR_MB=$(expr 1 + $FOP_CUR_SIZE / 1048576)
        printf "    %s %s: %s MB written to %s        \r" \
            "$ANIM_STR" "$FOP_MSG" "$FOP_CUR_MB" "$FOP_BASENAME"

        # Doze a bit to let the file size increase
        # (SLEEP_TIME defaults to 1s if unset)
        sleep ${SLEEP_TIME-"1s"}

        FOP_LAST_SIZE=$FOP_CUR_SIZE 
        FOP_CUR_SIZE=$(du -b "$FOP_OUTFILE" | awk '{print $1}')
    done
    printf "\n\n"
}

# ******************************************************************************
# Check for a group of run-time dependencies.
# Input args: $1 = name of dependency group (eg dvd or magick or core)
#             $2 = a quoted, space-separated list of binaries in that group
#                  eg: "spumux dvdauthor growisofs"
# Run-time dependencies are checked in two stages: First, tovid-init looks for
# the required binaries and writes a full report in $TOVID_HOME/deps.list
# From there, individual scripts can check for either an individual dependency or
# for a group of dependencies with one line:
#   individual binary case:
#     if ! grep "foo.*ok" $DEP_LIST_FILE; then exit 1; fi
#   group case:
#     if ! grep "have.*foo" $DEP_LIST_FILE; then exit 1; fi
#     (DEP_GROUPS below holds the current list of dependency group)
#
# Other details: tovid-init skips checking for dependencies if the file 
# $DEP_FILE_LIST exists (saves a few redudancies). However, if a user passes 
# -refresh-deps to tovid-init from a prompt, then tovid-init will re-check and 
# re-write the file.
# ******************************************************************************
function check_dep_group()
{
#save_IFS=$IFS
#IFS=' '

group_name="$1"
dep_list="$2"

GROUP_PASS=:
for DEP in $dep_list; do
  HAVE_DEP=$(which -a $DEP)
  NUM_DEP=$(echo "$HAVE_DEP" | wc -l)
  if test -z "$HAVE_DEP"; then
     DEP_STATUS="MISSING"
     DEP_MSG=$(echo ${DEP}_msg)
     DEP_MSG=$(eval echo $DEP_MSG)
     GROUP_PASS=false
  elif test "$NUM_DEP" -eq 1; then
     DEP_STATUS="ok"
     DEP_MSG="$HAVE_DEP"
  else
     DEP_STATUS="ok, using"
     FOUND_DEPS=$(echo -n "$HAVE_DEP" | tr '\n' ' ')
     DEP_MSG=$(which $DEP)
     for FOUND_DEP in ${FOUND_DEPS[@]}; do
         DEP_MSG=$(echo "$DEP_MSG"; printf "  %-11s %+9s  %s" " " " " "found: $FOUND_DEP")
     done
  fi
  printf "  %-11s %+9s: %s\n" "$DEP" "$DEP_STATUS" "$DEP_MSG" >> $DEP_LIST_FILE
  j=$(expr $j \+ 1)
done

echo >> $DEP_LIST_FILE
if $GROUP_PASS; then DEP_SUMMARY="$DEP_SUMMARY$group_name "; fi

#IFS=$save_IFS
}

# ******************************************************************************
# Check to see if a dependency group is in the dependency file, quit if missing
# Input args: $1 = dependency group to check for
#             $2 = Descriptive message about what the user needs the 
#             dependencies for
#
# ******************************************************************************
function assert_dep_group ()
{
DEP_TO_CHECK="$1"
DEP_MESSAGE="$2"

MISSING_DEPS=`cat $DEP_LIST_FILE | tr '\n' '\t' | awk -F "# $DEP_TO_CHECK" '{ print $2 }' | \
              awk -F '\t\t#' '{ print $1 }' | tr '\t' '\n' | grep "MISSING"`

if ! grep -q "have.*$DEP_TO_CHECK" $DEP_LIST_FILE; then
    echo "$DEP_MESSAGE"
    echo
    echo "$MISSING_DEPS"
    echo
    precho "Please install the above MISSING dependencies, run 'tovid -refresh-deps', and try again."
    exit 1;
fi
}

# ******************************************************************************
# Check to see if a dependency is in the dependency file, quit if missing
# Input args: $1 = dependency to check for
#             $2 = Descriptive message about what the user needs the 
#                  dependencies for
# 
# ******************************************************************************
function assert_dep ()
{
DEP_TO_CHECK="$1"
DEP_MESSAGE="$2"

MISSING_DEP=$(grep "$DEP_TO_CHECK" "$DEP_LIST_FILE")

if grep -q "$DEP_TO_CHECK.*MISSING" "$DEP_LIST_FILE"; then
    echo "$DEP_MESSAGE"
    echo
    echo "$MISSING_DEP"
    echo
    precho "Please install the above MISSING dependency, run 'tovid -refresh-deps', and try again."
    exit 1;
fi
}

# ******************************************************************************
# Countdown 5 seconds, then return
# TODO: Take argument # of seconds
# ******************************************************************************
function countdown()
{
    for _CNTR in 5 4 3 2 1; do
        echo -n -e " in $_CNTR seconds...\r"
        sleep 1s
    done
}

# ******************************************************************************
# ******************************************************************************
#
#
# EXECUTED INITIALIZATION
#
#
# ******************************************************************************
# ******************************************************************************

REFRESH_DEPS=false

# Trap Ctrl-C and TERM to clean up child processes
trap 'killsubprocs; exit 13' TERM INT

# ******************************************************************************
# Read command-line arguments.
# NOTE: cannot check for bad ones here b/c other scripts' options will pass thru
# here when tovid-init is sourced. Must be very hands off.
if test x"$1" = x"-refresh-deps"; then
   REFRESH_DEPS=:
fi

# ******************************************************************************
# Platform-specific initialization
# Determines host platform and configures things accordingly
# ******************************************************************************
KERNEL=$(uname)
if test "$KERNEL" = "Linux"; then
    # Linux should have /proc/cpuinfo
    CPU_MODEL=$(awk -F ":" '/model name/ {print $2}' /proc/cpuinfo)
    CPU_SPEED=$(awk 'BEGIN { IGNORECASE = 1 } /MHz/ { print $4 }' /proc/cpuinfo)
    # Test for multiple CPUs. If they are available, try to use them.
    if test $(grep processor /proc/cpuinfo | wc -l) -ge "2"; then
        MULTIPLE_CPUS=:
    else
        MULTIPLE_CPUS=false
    fi
elif test "$KERNEL" = "Darwin"; then
    :
fi


# ******************************************************************************
# tovid home setup
# ******************************************************************************

# Make home!
if test -d "$TOVID_HOME"; then :
else
  mkdir "$TOVID_HOME"
fi

# Config file configuration and creation
CONFIG_FILE=$TOVID_HOME/tovid.config

if test -f $CONFIG_FILE; then :
else
  CONFIG_CONTENTS=`cat << EOF
tovid
# Sample tovid configuration file
# Each line may have one or more tovid options
# This file is read EVERY time tovid runs
# DO NOT COMMENT IN LINE

# See 'man tovid' for a complete list of options

# Disc type
#-dvd
#-half-dvd
#-dvd-vcd
#-vcd
#-svcd
#-kvcd
#-ksvcd
#-kdvd
    
# TV system standard
#-pal
#-ntsc
#-ntscfilm
EOF`
  printf "$CONFIG_CONTENTS\n" > "$CONFIG_FILE"
fi

# Working directory configuration
USER_PREFS=$TOVID_HOME/preferences

# Default working/output directories
WORKING_DIR=$PWD
OUTPUT_DIR=$PWD

# If prefs file exists, read it
if test -f $USER_PREFS; then
    eval $(grep -v ^# $USER_PREFS)
# Otherwise, create a default prefs file
else
    PREFS_CONTENTS=`cat << EOF
# tovid preferences
# Configures working/output directories for tovid
#WORKING_DIR=/tmp
#OUTPUT_DIR=/video/outfiles
EOF`
    printf "$PREFS_CONTENTS\n" > "$USER_PREFS"
fi

# ******************************************************************************
# Check for run-time dependencies
# ******************************************************************************

DEP_LIST_FILE=$TOVID_HOME/dependency.list
DEP_LIST_VERSION=$( awk -F ': ' '/tovid version/ { print $2 }' $DEP_LIST_FILE)

if $REFRESH_DEPS || test "$DEP_LIST_VERSION" != "$TOVID_VERSION" ; then
  echo "Removing old dependency list file..."
  rm -fv $DEP_LIST_FILE
fi

if ! test -f $DEP_LIST_FILE
then
  echo "Generating dependency list file..."
# Adding (or removing) dependencies:
# (1) Does it belong to an existing group? If so, then add it to the list in
#     [GROUP]_DEPS. If not, you'll need to add a new group. (keep reading...)
# (2) Add a help message if you want, and when tovid-init cannot find the
#     dependency, it will add this message to DEP_LIST_FILE. If you added 
#     "bar" to CORE_DEPS, then make a new string called "bar_msg". 
#     There, you're done!
#
# Adding (or removing) a dependency group:
# (1) Add another group name to DEP_GROUPS (eg add "fish")
# (2) Poplulate that group with a new space-separated string of binaries to 
#     look for (eg make FISH_DEPS="carp tuna salmon")
# (3) Add help messages for those new dependencies (so carp_msg, tuna_msg, and
#     salmon_msg)
# (4) Add the new dependency string of binaries to the DEP_GROUP_MEMBERS array.
#     (eg add [5]="$FISH_DEPS").
# (5) Add a comment that describes the new dependency group to the
#     DEP_GROUP_COMMENTS array. (eg add [5]="# needed to have a fish fry")

# List all the dependency groups. When every member of one group is found,
# the name of the group will be added to the summary section of $DEP_LIST_FILE
# for group grepping a la: if ! grep "have.*foo" $DEP_LIST_FILE; then exit 1; fi
DEP_GROUPS=(core magick dvd vcd transcode plugins)

# ******************************************************************************
# Required Dependencies 
# ******************************************************************************
CORE_DEPS="grep sed md5sum mplayer mencoder mplex mpeg2enc yuvfps yuvdenoise ppmtoy4m mp2enc jpeg2yuv ffmpeg"
grep_msg="a GNU utility (www.gnu.org/software/grep)"
sed_msg="a GNU utility (directory.fsf.org/GNU/sed.html)"
md5sum_msg="a GNU utility (www.gnu.org/software/coreutils)"
mplayer_msg="part of mplayer (www.mplayerhq.hu)"
mencoder_msg="$mplayer_msg"
mplex_msg="part of mjpegtools (mjpeg.sf.net}"
mpeg2enc_msg="$mplex_msg"
yuvfps_msg="$mplex_msg"
yuvdenoise_msg="$mplex_msg"
ppmtoy4m_msg="$mplex_msg"
mp2enc_msg="$mplex_msg"
jpeg2yuv_msg="$mplex_msg"
ffmpeg_msg="a video encoding utility (ffmpeg.sf.net)"

# ******************************************************************************
# Optional Dependencies (not quite optional yet...)
# ******************************************************************************
# Optional dependencies are grouped according to the functionality they bring
# to tovid: menu creation, DVD creation, (S)VCD creation, and post-processing.

# ------------------------------------------------------------------------------
# ImageMagick components
MAGICK_DEPS="composite convert"
composite_msg="part of ImageMagick (www.imagemagick.org)"
convert_msg="$composite_msg"

# ------------------------------------------------------------------------------
# dvdauthor compononets
# (note: growisofs is NOT distributed with dvdauthor, but for tovid's
# purposes, it fits in the same catagory, as it burns DVDs!)
DVD_DEPS="spumux dvdauthor growisofs"
spumux_msg="part of dvdauthor (dvdauthor.sf.net)"
dvdauthor_msg="$spumux_msg"
growisofs_msg="part of dvd+rw-tools: (fy.chalmers.se/~appro/linux/DVD+RW)"

# ------------------------------------------------------------------------------
# vcdimager components
# (note: cdrdao is NOT distributed with vcdimager, but for tovid's
# purposes, it fits in the same catagory, as it burns (S)VCDs!)
VCD_DEPS="vcdxbuild cdrdao"
vcdxbuild_msg="part of vcdimager (www.vcdimager.org)"
cdrdao_msg="a standalone package (cdrdao.sf.net)"

# ------------------------------------------------------------------------------
# transcode components
TRANSCODE_DEPS="tcprobe tcrequant"
tcprobe_msg="part of transcode (www.transcoding.org)"
tcrequant_msg="$tcprobe_msg"

# ------------------------------------------------------------------------------
# Plugin tools
PLUGIN_DEPS="sox normalize"
sox_msg="swiss army knife for sound (sox.sf.net)"
normalize_msg="wave gain and normalization (normalize.nongnu.org)"

# Construct an array of each dependency group's members. Conveniently holds
# ALL of tovid's run-time dependencies (echo ${DEP_GROUP_MEMBERS[@]}).
DEP_GROUP_MEMBERS=([0]="$CORE_DEPS" [1]="$MAGICK_DEPS" [2]="$DVD_DEPS" [3]="$VCD_DEPS" [4]="$TRANSCODE_DEPS" [5]="$PLUGIN_DEPS")

# Comments describing each group of dependencies. Note the leading '#' isn't
# necessary as this file isn't sourced, but it helps with readability.
DEP_GROUP_COMMENTS=([0]="# core" [1]="# magick (needed to make menus and slideshows)" [2]="# dvd (needed to make DVD menus and burn DVDs)" [3]="# vcd (needed to burn (S)VCDs)" [4]="# transcode (needed for animated menus)" [5]="# plugins (needed for other specific tasks)")

# Check for deps and write the list file.
echo "# tovid version: $TOVID_VERSION" >> "${DEP_LIST_FILE}"
i=0
for dep_group in ${DEP_GROUPS[@]}; do
  echo "${DEP_GROUP_COMMENTS[i]}" >> "${DEP_LIST_FILE}"
  check_dep_group "$dep_group" "${DEP_GROUP_MEMBERS[i]}"
  let i=i+1
done

echo "# summary" >> $DEP_LIST_FILE
echo "have: $DEP_SUMMARY" >> $DEP_LIST_FILE
fi

# Quit and complain if ANY core dependency is missing.
assert_dep_group "core" "You are missing tovid CORE dependencies!"

BUILD_OPTIONS=$(tail -n 1 $DEP_LIST_FILE | sed s/have/with/)

# End tovid-init
